-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

-- Description: jtag链路测试
local jtag_test = {}

local log = require 'mc.logging'
local ipmi = require 'ipmi'
local comp_code = ipmi.types.Cc
local unit_manager = require 'unit_manager.unit_manager'
local msg = require 'general_hardware.ipmi.ipmi_message'
local drivers = require 'unit_manager.class.logic_fw.upgrade.drivers_api'
local bs = require 'mc.bitstring'

local BOARD_TYPE_BACK<const> = 1
local BOARD_TYPE_BASE<const> = 3
local BOARD_TYPE_L1<const> = 42
local MANUFACTURE_ID <const> = 0x0007DB

jtag_test.type = -1
jtag_test.slot = -1

local function get_cpld_device_by_channel(list, channel)
    for _, v in pairs(list) do
        if channel == v.csr.FirmwareRoute and v.csr.Routes ~= 255  then
            return v
        end
    end
end

local function get_cpld_device_by_type_slot(list, type, slot)
    for _, v in pairs(list) do
        if type == v.csr.ComponentType and slot == v.csr.ComponentSlot then
            return v
        end
    end
end

local function get_cpld_channel(fw_list)
    for _, v in pairs(fw_list) do
        if v.csr.Routes ~= 255 then
            return v.csr.Routes
        end
    end
end

local function support_auto_switch_jtag(fw_list, type, slot)
    for _, v in pairs(fw_list) do
        if type == v.csr.ComponentType and slot == v.csr.ComponentSlot then
            return true
        end
    end
    return false
end

local function parse_device_id(device)
    local rsp = {}
    for i = 1, device.device_count do
        table.insert(rsp, string.pack('B', i))
        local device_id = device[string.format('device%d_id', i)]
        table.insert(rsp, string.pack("I4", device_id))
        log:notice("get ManufactureId successfully, cpld%s ManufactureId is %s", i, device_id)
    end
    return rsp
end

local channel_type_table = {
    [0] = 2,   -- Front Hdd Backplane
    [2] = 3,   -- Inner Hdd Backplane
    [3] = 4,   -- PSU Backplane
    [21] = 1,  -- Basic Computing Board
    [22] = 5,  -- Fan Backplane
    [255] = 0  -- Default Channel
}

local channel_component_table = {
    [42] = 7   -- L1交换板
}

local channel_name_table = {
    [0] = "Default Channel",
    [1] = "Basic Computing Board",
    [2] = "Front Hdd Backplane",
    [3] = "Inner Hdd Backplane",
    [4] = "PSU Backplane",
    [5] = "Fan Backplane",
    [7] = "L1 Backplane"
}

local req_tail_code = bs.new("<<component_id, channel>>")
function jtag_test.set_cpld_channel(req, ctx)
    if req.ManufactureId ~= MANUFACTURE_ID then
        ipmi.ipmi_operation_log(ctx, 'General_hardware', "Set cpld channel failed")
        log:error('[General_hardware]ManufacturerId %s Invalid', req.ManufactureId)
        return msg.SetCpldChannelRsp.new(comp_code.InvalidFieldRequest, MANUFACTURE_ID, 0)
    end

    local channel_type = req.ChannelType
    local tmp_channel_type
    local fw_list = unit_manager.get_instance().logic_fw
    -- channel_type为0xFE时通过Component Type确定测试对象
    -- 其他数值时根据board表格找到对应通道进行切换
    if channel_type == 254 then
        local tail = req_tail_code:unpack(req.Tail)
        local type = tail.component_id
        local slot = tail.channel
        -- 记录切换的通道，为自动切换的jtag通路提供入参
        jtag_test.type = type
        jtag_test.slot = slot
        -- 支持自动切换的jtag直接返回成功和切换后的通道槽位号,否则根据Component表格进行切换
        if support_auto_switch_jtag(fw_list, type, slot) then
            return msg.SetCpldChannelRsp.new(comp_code.Success, MANUFACTURE_ID, slot)
        end
        tmp_channel_type = channel_component_table[type]
    else
        tmp_channel_type = channel_type_table[channel_type]
    end
    if not channel_type or not tmp_channel_type or not channel_name_table[tmp_channel_type] then
        ipmi.ipmi_operation_log(ctx, 'General_hardware', "Set cpld channel failed")
        log:error('channel type %s not in support list', channel_type)
        return msg.SetCpldChannelRsp.new(comp_code.UnspecifiedError, MANUFACTURE_ID, 0)
    end
    local channel_name = channel_name_table[tmp_channel_type]
    local fw = get_cpld_device_by_channel(fw_list, tmp_channel_type)

    if not fw then
        ipmi.ipmi_operation_log(ctx, 'General_hardware', "Set cpld channel to %s failed", channel_name)
        log:error('get jatg switch %s firmware object failed', tmp_channel_type)
        return msg.SetCpldChannelRsp.new(comp_code.InvalidFieldRequest, MANUFACTURE_ID, 0)
    end
    local ok, res = pcall(function ()
        fw.csr.Routes = fw.firmware_route
    end)
    if not ok then
        ipmi.ipmi_operation_log(ctx, 'General_hardware', "Set cpld channel to %s failed", channel_name)
        log:error('set jatg switch %s failed, err is %s', tmp_channel_type, res)
        return msg.SetCpldChannelRsp.new(comp_code.UnspecifiedError, MANUFACTURE_ID, 0)
    end
    ipmi.ipmi_operation_log(ctx, 'General_hardware', "Set cpld channel to %s successfully", channel_name)
    log:notice("set cpld channel to channel = %s, name = %s successfully", tmp_channel_type, channel_name)
    return msg.SetCpldChannelRsp.new(comp_code.Success, MANUFACTURE_ID, fw.firmware_route)
end

-- 根据componettype 获取单板所有支持切换的jtag通道
local req_get_device_tail = bs.new("<<component_id, tail/string>>")
function jtag_test.get_cpld_channel_info(fw_list, req)
    local tail = req_get_device_tail:unpack(req.Tail)
    local type = tail.component_id

    local channel_list = {}
    for _, v in pairs(fw_list) do
        if v.csr.ComponentType == type and type ~= 0 then
            table.insert(channel_list, v.csr.ComponentSlot)
        end
    end

    table.sort(channel_list)
    log:notice("get component_id = %s cpld channel,channel num is %s", type, #channel_list)
    local resp = ""
    for _, v in pairs(channel_list) do
        resp = resp .. string.pack('B', v)
    end
    return msg.GetCpldDeviceidInfoRsp.new(comp_code.Success, MANUFACTURE_ID, type,
        #channel_list, resp)
end

function jtag_test.get_cpld_deviceid_info(req, ctx)
    -- 限制改命令厂商必须为 Huawei
    if req.ManufactureId ~= MANUFACTURE_ID then
        log:error('[General_hardware]ManufacturerId %s Invalid', req.ManufactureId)
        return msg.GetCpldDeviceidInfoRsp.new(comp_code.InvalidFieldRequest, MANUFACTURE_ID, 0, 0, '')
    end

    local board_type = req.BoardType
    local fw_list = unit_manager.get_instance().logic_fw
    local channel_type = 0
    local cpld = nil
    -- board_type为255时,返回cpld通道个数和列表
    -- 硬件上定义，直连的jtag为底板，对应这里的3
    -- 其他非直连bmcjtag的板子都为背板，对应type为1
    -- 除了1/3/42场景，其他时候表示Component type
    if board_type == 255 then
        return jtag_test.get_cpld_channel_info(fw_list, req)
    elseif board_type == BOARD_TYPE_BACK or board_type == BOARD_TYPE_L1 then
        channel_type = get_cpld_channel(fw_list)
        cpld = get_cpld_device_by_channel(fw_list, channel_type)
    elseif board_type == BOARD_TYPE_BASE then
        channel_type = 0
        cpld = get_cpld_device_by_channel(fw_list, 0)
    elseif board_type == jtag_test.type then
        channel_type = jtag_test.slot
        cpld = get_cpld_device_by_type_slot(fw_list, board_type, channel_type)
    else
        log:error('component type %s is not match', board_type)
        return msg.GetCpldDeviceidInfoRsp.new(comp_code.InvalidFieldRequest, MANUFACTURE_ID, 0, 0, '')
    end

    if not cpld then
        log:error('get cpld device info fail')
        return msg.GetCpldDeviceidInfoRsp.new(comp_code.UnspecifiedError, MANUFACTURE_ID, 0, 0, '')
    end
    log:notice("match cpld successfully, board_type = %s, channel_type = %s, cpld name is %s, softwareid is %s",
        board_type, channel_type, cpld.csr.Name, cpld.csr.SoftwareId)
    -- 根据cpld获取cpld厂商，按照四个字节进行封装
    local cpld_device = {}
    drivers:get_cpld_device_info(cpld.csr.ChipInfo, cpld_device)

    return msg.GetCpldDeviceidInfoRsp.new(comp_code.Success, MANUFACTURE_ID, channel_type,
        cpld_device.device_count, table.concat(parse_device_id(cpld_device)))
end

return jtag_test